Skip to content

feat: Kani harnesses for scheduler + codegen invariants (#136)#141

Open
avrabe wants to merge 2 commits intomainfrom
fix/136-kani-harnesses
Open

feat: Kani harnesses for scheduler + codegen invariants (#136)#141
avrabe wants to merge 2 commits intomainfrom
fix/136-kani-harnesses

Conversation

@avrabe
Copy link
Copy Markdown
Contributor

@avrabe avrabe commented Apr 23, 2026

Summary

  • 4 Kani proof harnesses (3 for solver, 1 for codegen)
  • Each header cites the Lean theorem it mirrors
  • New CI kani job with continue-on-error: true (first-pass bounds)
  • Rivet traceability via proof_link: fields

Harnesses

Harness Mirrors
kani_schedule_implies_no_deadline_miss RMBound.lean:49 (rmBound_ge_ln2) + RMBound.lean:76 (rm_single_task)
kani_priority_monotonicity RTA.lean:74 (rtaStep_mono) + RTA.lean:65 (totalInterference_mono)
kani_zero_laxity_handled EDF.lean:88 (edf_two_tasks_demand) + RMBound.lean:38 (rmBound_one)
kani_emit_preserves_schedule RTA.lean:186 (rta_finds_least_fixed_point)

Design note

la_arena::Idx is opaque to Kani's symbolic execution — production types ThreadConstraint/SystemInstance cannot be symbolically constructed. Per task constraint (no production-source changes), harnesses verify the same mathematical invariants using local pure-function mirrors that match production to 6 decimal places (numeric constants derive from rma_utilization_bound(n) in spar-analysis/scheduling.rs:522).

Local verification

  • cargo build --tests -p spar-solver -p spar-codegen PASS (Kani files are empty modules under default cfg)
  • cargo clippy --workspace --all-targets -- -D warnings PASS
  • cargo fmt --all -- --check PASS
  • cargo test -p spar-solver -p spar-codegen --tests PASS (existing tests; Kani files report 0 tests as expected)
  • rivet validate PASS (91 warnings, no new errors)
  • cargo kani --tests not run locally (sandbox blocked) — CI is the verifier

Why continue-on-error: true

Initial unwind bounds may need tuning per harness; we collect data first, then hard-gate in a follow-up. Plan documented in workflow comment.

Test plan

  • kani CI job runs (success preferred but non-blocking)
  • Format / Clippy / Test green
  • Reviewer cross-checks each harness header against the cited Lean line

🤖 Generated with Claude Code

@codecov
Copy link
Copy Markdown

codecov Bot commented Apr 23, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

avrabe and others added 2 commits April 24, 2026 06:14
Adds bounded Kani proof harnesses for spar-solver and spar-codegen,
each citing the Lean theorem it mirrors.

spar-solver/tests/kani_solver.rs:
- kani_schedule_implies_no_deadline_miss   mirrors RMBound.lean:49 + 76
- kani_priority_monotonicity                mirrors RTA.lean:74 + 65
- kani_zero_laxity_handled                  mirrors EDF.lean:88 + RMBound.lean:38

spar-codegen/tests/kani_codegen.rs:
- kani_emit_preserves_schedule              mirrors RTA.lean:186

All harnesses use #[kani::proof] + #[kani::unwind(8)] with MAX_TASKS=4.

Production source unchanged. Kani-only deps gated behind
`[target.'cfg(kani)'.dev-dependencies]`. Kani-cfg lint allowance added.

CI: new `kani` job in ci.yml with `continue-on-error: true` while bounds
are tuned. Comment in workflow describes hard-gate plan.

Traceability in `artifacts/verification.yaml`: 4 VAL-KANI-* entries with
`proof_link:` fields pointing to the Lean theorems.

Closes #136.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The actual Kani CI failure on this PR was a single unwinding-assertion
violation in `kani_emit_preserves_schedule`:

  Check 275: emit.unwind.0
   - Status: FAILURE
   - Description: "unwinding assertion loop 0"
   - Location: crates/spar-codegen/tests/kani_codegen.rs:86 (header iter)

All four `kani_zero_laxity_handled` assertions actually verified
SUCCESSFULLY (Checks 10-15) — those assertions are constructed
deterministically with `period=1000, wcet=500` so `total_util_ppm` is
exactly 1_000_000 by construction. The "Description" lines in CBMC
output describe what each check verifies, not the verdict; every
`Status:` for those checks is SUCCESS.

The real problem: `unwind=8` was insufficient for the 9-byte header
iterator, the 11-byte tokens iterator, and especially the 64-byte
`emit_len`/`contains` scan loops. CBMC needs `loop_bound + 1` unwinds.

Fix: bump the codegen harness `#[kani::unwind]` to 65 and pull the
buffer size into a named constant `BUF_SIZE` so the relationship
`UNWIND_N = BUF_SIZE + 1` is documented. This follows the existing
`kani_solver.rs` guidance ("If harnesses fail with unwinding assertion
violation, raise UNWIND_N rather than the loop bound"). The proof
obligations (header presence, task-ID containment, processor-ID
containment) are unchanged — only the unwinder gets enough budget to
fully explore the bounded loops.

No production code changes. The solver harnesses already pass at
unwind=8 and are untouched.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@avrabe avrabe force-pushed the fix/136-kani-harnesses branch from bf157b3 to dd0e88c Compare April 24, 2026 04:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant